#include <string.h>
#include "common.h"
#include "scanner.h"

Scanner scanner;
const char* keywords[] = {
    "var", "fun", "class", "if", "else", 
    "while", "for", "this", "super","return", 
    "and", "or", "nil", "print"
};
TokenType keywordTypes[] = {
    TOKEN_VAR, TOKEN_FUN, TOKEN_CLASS, TOKEN_IF, TOKEN_ELSE,
    TOKEN_WHILE, TOKEN_FOR, TOKEN_THIS, TOKEN_SUPER, TOKEN_RETURN,
    TOKEN_AND, TOKEN_OR, TOKEN_NIL, TOKEN_PRINT
};
size_t keywordsNum = 14;

bool isAtEnd();
Token makeToken(TokenType type);
void scanWhitespace();
char advance();
char peek();
char peekNext();
bool match(char c);
Token string();
Token number();
Token identifier();
Token makeErrorToken(const char* msg);
bool isNumber(char c);
bool isAlpha(char c);

void initScanner(const char* src) {
    scanner.current = src;
    scanner.start = src;
    scanner.line = 1;
}

Token scanToken() {
    scanWhitespace();
    scanner.start = scanner.current;
    if(isAtEnd())
        return makeToken(TOKEN_EOF);
    
    char c = advance();
    if(isNumber(c)) return number();
    if(isAlpha(c)) return identifier();
    switch (c)
    {
        case '+':
            return makeToken(TOKEN_PLUS);
        case '-':
            return makeToken(TOKEN_MINUS);
        case '*':
            return makeToken(TOKEN_STAR);
        case '/':
            return makeToken(TOKEN_SLASH);
        case '.':
            return makeToken(TOKEN_DOT);
        case ',':
            return makeToken(TOKEN_COMMA);
        case ';':
            return makeToken(TOKEN_SEMICOLON);
        case '(':
            return makeToken(TOKEN_LEFT_PAREN);
        case ')':
            return makeToken(TOKEN_RIGHT_PAREN);
        case '{':
            return makeToken(TOKEN_LEFT_BRACE);
        case '}':
            return makeToken(TOKEN_RIGHT_BRACE);
        case '=':
            return match('=') ? makeToken(TOKEN_EQUAL_EQUAL) : makeToken(TOKEN_EQUAL);
        case '>':
            return match('=') ? makeToken(TOKEN_GREATER_EQUAL) : makeToken(TOKEN_GREATER);
        case '<':
            return match('=') ? makeToken(TOKEN_LESS_EQUAL) : makeToken(TOKEN_LESS);
        case '!':
            return match('=') ? makeToken(TOKEN_BANG_EQUAL) : makeToken(TOKEN_BANG);
        case '"':
            return string();
    }
    
}

bool isAtEnd() {
    return *scanner.current == '\0';
}

Token makeToken(TokenType type) {
    Token token;
    token.start = scanner.start;
    token.len = scanner.current - scanner.start;
    token.type = type;
    token.line = scanner.line;
    return token;
}

void scanWhitespace() {
    while (!isAtEnd())
    {
        char c = peek();
        switch (c)
        {
        case ' ':
        case '\t':
            advance();
            break;
        case '\n':
            scanner.line++;
            advance();
            break;
        case '/':
            if(peekNext() == '/') {
                while (!isAtEnd() && peek() != '\n')
                {
                    advance();
                }
                break;
            }
        default:
            return;
        }
    }
    
}

char advance() {
    return *scanner.current++;
}

char peek() {
    return *scanner.current;
}

char peekNext() {
    return scanner.current[1];
}

bool match(char c) {
    if(isAtEnd()) return false;
    if(peek() == c) {
        advance();
        return true;
    }
    return false;
}

Token string() {
    while (!isAtEnd() && peek() != '"')
    {
        if(peek() == '\n')
            scanner.line++;
        advance();
    }
    if(isAtEnd())
        return makeErrorToken("Unterminated string!");
    advance();
    return makeToken(TOKEN_STRING);
}

bool isNumber(char c) {
    return '0' <= c && c <= '9';
}

bool isAlpha(char c) {
    return 'a' <= c && c <= 'z'
        || 'A' <= c && c <= 'Z'
        || c == '_';
}

Token number() {
    while (!isAtEnd() && isNumber(peek()))
    {
        advance();
    }
    if(peek() == '.') {
        advance();
        while (!isAtEnd() && isNumber(peek()))
        {
            advance();
        }
    }
    return makeToken(TOKEN_NUMBER);
}

Token identifier() {
    while (isAlpha(peek()) || isNumber(peek()))
    {
        advance();
    }
    for (size_t i = 0; i < keywordsNum; i++)
    {
        const char* keyword = keywords[i];
        size_t keyword_len = strlen(keyword);
        if(scanner.current - scanner.start == keyword_len && memcmp(scanner.start, keyword, keyword_len) == 0) {
            // printf("find keyword: %s %.*s\n", keyword, keyword_len, scanner.start);
            return makeToken(keywordTypes[i]);
        }
    }
    return makeToken(TOKEN_IDENTIFIER);
}

Token makeErrorToken(const char* msg) {
    Token token;
    token.start = msg;
    token.len = strlen(msg);
    token.line = scanner.line;
    token.type = TOKEN_ERROR;
    return token;
}

void printToken(Token token) {
    printf("%d, %d, %.*s\n", 
            token.line, token.type, token.len, token.start);
}
